﻿using System;
using System.Linq;

namespace Reloaded.MWOSiteReader
{
	/// <summary>
	/// Wrap the MWOMERCS site statistics with a simple scraper
	/// </summary>
	public class MWOPilotStatistics
	{
		/// <summary>
		/// The base source Uri
		/// </summary>
		private readonly System.Uri _baseUri;

		/// <summary>
		/// A shared http client instance for the life of the object
		/// </summary>
		private readonly System.Net.Http.HttpClient _httpClient;

		/// <summary>
		/// Constructor
		/// </summary>
		public MWOPilotStatistics(System.Uri sourceBaseUri)
			: this()
		{
			_baseUri = sourceBaseUri;

			_httpClient = new System.Net.Http.HttpClient(
				new System.Net.Http.HttpClientHandler
				{
					AllowAutoRedirect = true,
					UseCookies = true
				});
		}

		/// <summary>
		/// Constructor
		/// </summary>
		private MWOPilotStatistics()
		{
			Trace = new System.Diagnostics.TraceSource(this.GetType().FullName);
		}

		/// <summary>
		/// Diagnostics trace
		/// </summary>
		protected System.Diagnostics.TraceSource Trace { get; private set; }

		/// <summary>
		/// Callsign detected in Login
		/// </summary>
		/// <remarks>Not intended for use outside of local diagnostics</remarks>
		private string PilotCallsign { get; set; }

		/// <summary>
		/// Get base pilot statistics
		/// </summary>
		public BaseStatistics GetBaseStatistics()
		{
			var baseStatsUri = new Uri(_baseUri, new Uri("/profile/stats", UriKind.Relative));
			BaseStatistics result = null;

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Retrieving base statistics for: {0}", PilotCallsign);

			var theRequest = _httpClient.GetStreamAsync(baseStatsUri).ContinueWith(s =>
			{
				var theDocument = new HtmlAgilityPack.HtmlDocument();
				theDocument.Load(s.Result);
				theDocument.OptionFixNestedTags = true;

				var baseStats = theDocument.DocumentNode
					.Descendants("table")
					.FirstOrDefault()
					.Descendants("tbody")
					.FirstOrDefault()
					.Descendants("tr")
					.Select(dn => new
					{
						Label = dn.Descendants("td").First().InnerText,
						Value = dn.Descendants("td").Last().InnerText,
					});

				//	Current statistics snapshot
				result = new BaseStatistics
				{
					CBills = long.Parse(baseStats.First(t => t.Label.Equals("C-Bills")).Value.Trim(), System.Globalization.NumberStyles.AllowThousands),
					Deaths = long.Parse(baseStats.First(t => t.Label.Equals("Kills / Death")).Value.Split('/')[1].Trim(), System.Globalization.NumberStyles.AllowThousands),
					Kills = long.Parse(baseStats.First(t => t.Label.Equals("Kills / Death")).Value.Split('/')[0].Trim(), System.Globalization.NumberStyles.AllowThousands),
					Losses = long.Parse(baseStats.First(t => t.Label.Equals("Wins / Losses")).Value.Split('/')[1].Trim(), System.Globalization.NumberStyles.AllowThousands),
					Wins = long.Parse(baseStats.First(t => t.Label.Equals("Wins / Losses")).Value.Split('/')[0].Trim(), System.Globalization.NumberStyles.AllowThousands),
					XP = long.Parse(baseStats.First(t => t.Label.Equals("Experience Points")).Value.Trim(), System.Globalization.NumberStyles.AllowThousands),
				};
			});

			theRequest.Wait();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Complete retrieving base statistics for: {0}", PilotCallsign);

			if (theRequest.Exception != null)
				throw new Exception("Failed to parse source content: Base Statistics", theRequest.Exception);

			return (result);
		}

		/// <summary>
		/// Retrieve game mode statistics
		/// </summary>
		public System.Collections.Generic.List<GameModeStatistics> GetGameModeStatistics()
		{
			var modeStatsUri = new Uri(_baseUri, new Uri("/profile/stats?type=mode", UriKind.Relative));
			var result = new System.Collections.Generic.List<GameModeStatistics>();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Retrieving game mode statistics for: {0}", PilotCallsign);

			var theRequest = _httpClient.GetStreamAsync(modeStatsUri).ContinueWith(s =>
			{
				var theDocument = new HtmlAgilityPack.HtmlDocument();
				theDocument.Load(s.Result);
				theDocument.OptionFixNestedTags = true;

				result.AddRange(
						theDocument.DocumentNode
							.Descendants("table")
							.FirstOrDefault()
							.Descendants("tbody")
							.FirstOrDefault()
							.Descendants("tr")
							.Select(dn =>
							{
								var values = dn.Descendants("td").Select(td => td.InnerText).ToArray();

								if (values.Length == 1)
									//	When the pilot has no missions, there is a single cell with 'no data' text
									return (null);

								return new GameModeStatistics
								{
									GameMode = values[0],
									MatchesPlayed = long.Parse(values[1].Trim(), System.Globalization.NumberStyles.AllowThousands),
									Wins = long.Parse(values[2].Trim(), System.Globalization.NumberStyles.AllowThousands),
									Losses = long.Parse(values[3].Trim(), System.Globalization.NumberStyles.AllowThousands),
									XPEarned = long.Parse(values[5].Trim(), System.Globalization.NumberStyles.AllowThousands),
									CBillsEarned = long.Parse(values[6].Trim(), System.Globalization.NumberStyles.AllowThousands),
								};
							})
							.ToList()
					);
			});

			theRequest.Wait();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Completed retrieving game mode statistics for: {0}", PilotCallsign);

			if (theRequest.Exception != null)
				throw new Exception("Failed to parse source content: Game Mode Statistics", theRequest.Exception);

			return (result.Where(s => s != null).ToList());
		}

		/// <summary>
		/// Retrieve map statistics
		/// </summary>
		public System.Collections.Generic.List<MapStatistics> GetMapStatistics()
		{
			var mapStatsUri = new Uri(_baseUri, new Uri("/profile/stats?type=map", UriKind.Relative));
			var result = new System.Collections.Generic.List<MapStatistics>();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Retrieving map statistics for: {0}", PilotCallsign);

			var theRequest = _httpClient.GetStreamAsync(mapStatsUri).ContinueWith(s =>
			{
				var theDocument = new HtmlAgilityPack.HtmlDocument();
				theDocument.Load(s.Result);
				theDocument.OptionFixNestedTags = true;

				result.AddRange(
						theDocument.DocumentNode
							.Descendants("table")
							.FirstOrDefault()
							.Descendants("tbody")
							.FirstOrDefault()
							.Descendants("tr")
							.Select(dn =>
							{
								var values = dn.Descendants("td").Select(td => td.InnerText).ToArray();

								if (values.Length != 6)
									return null;

								return new MapStatistics
								{
									Map = values[0],
									MatchesPlayed = long.Parse(values[1].Trim(), System.Globalization.NumberStyles.AllowThousands),
									Wins = long.Parse(values[2].Trim(), System.Globalization.NumberStyles.AllowThousands),
									Losses = long.Parse(values[3].Trim(), System.Globalization.NumberStyles.AllowThousands),
									TimePlayed = (long)(TimeSpan.Parse(values[5].Replace("days", ".").Replace("day", ".").Replace(" ", "").Trim())).TotalSeconds,
								};
							})
							.Where(ms => ms != null)
							.ToList()
					);
			});

			theRequest.Wait();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Complete retrieving map statistics for: {0}", PilotCallsign);

			if (theRequest.Exception != null)
				throw new Exception("Failed to parse source content: Map Statistics", theRequest.Exception);

			return (result.Where(s => s != null).ToList());
		}

		/// <summary>
		/// Retrieve the contents of the pilots mech bays
		/// </summary>
		public System.Collections.Generic.List<MechBayContents> GetMechBayContents()
		{
			var profileUri = new Uri(_baseUri, new Uri("/profile", UriKind.Relative));
			var result = new System.Collections.Generic.List<MechBayContents>();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Retrieving mech bay contents for: {0}", PilotCallsign);

			var theRequest = _httpClient.GetStreamAsync(profileUri).ContinueWith(s =>
			{
				var theDocument = new HtmlAgilityPack.HtmlDocument();
				theDocument.Load(s.Result);
				theDocument.OptionFixNestedTags = true;

				var theMechBay = theDocument
					.GetElementbyId("mechBays");

				if (theMechBay != null)
				{
					result.AddRange(
						theMechBay
							.Descendants("div")
							.Where(d => d.Attributes.Contains("class") && d.Attributes["class"].Value.Split(' ').Any(c => c.Equals("mechBay")))
							.Select(d => new MechBayContents
							{
								CustomName = d.Descendants("div")
									.Where(dd => dd.Attributes.Contains("class") && dd.Attributes["class"].Value.Split(' ').Any(c => c.Equals("name")))
									.FirstOrDefault()
									.InnerText.Trim(),
								MechVariant = d.Descendants("div")
									.Where(dd => dd.Attributes.Contains("class") && dd.Attributes["class"].Value.Split(' ').Any(c => c.Equals("mechname")))
									.FirstOrDefault()
									.InnerText.Trim(),
							})
						);
				}
			});

			theRequest.Wait();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Completed retrieving mech bay contents for: {0}", PilotCallsign);

			if (theRequest.Exception != null)
				throw new Exception("Failed to parse source content: Mech Bay Contents", theRequest.Exception);

			return (result.Where(s => s != null).ToList());
		}

		/// <summary>
		/// Retrieve mech variant statistics
		/// </summary>
		public System.Collections.Generic.List<MechVariantStatistics> GetMechStatistics()
		{
			var mechStatsUri = new Uri(_baseUri, new Uri("/profile/stats?type=mech", UriKind.Relative));
			var result = new System.Collections.Generic.List<MechVariantStatistics>();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Retrieving mech variant statistics for: {0}", PilotCallsign);

			var theRequest = _httpClient.GetStreamAsync(mechStatsUri).ContinueWith(s =>
			{
				var theDocument = new HtmlAgilityPack.HtmlDocument();
				theDocument.Load(s.Result);
				theDocument.OptionFixNestedTags = true;

				result.AddRange(
					theDocument.DocumentNode
						.Descendants("table")
						.FirstOrDefault()
						.Descendants("tbody")
						.FirstOrDefault()
						.Descendants("tr")
						.Select(dn =>
						{
							var values = dn.Descendants("td").Select(td => td.InnerText).ToArray();

							if (values.Length == 1)
								//	When the pilot has no missions, there is a single cell with 'no data' text
								return (null);

							return new MechVariantStatistics
							{
								MechVariant = values[0],
								MatchesPlayed = long.Parse(values[1].Trim(), System.Globalization.NumberStyles.AllowThousands),
								Wins = long.Parse(values[2].Trim(), System.Globalization.NumberStyles.AllowThousands),
								Losses = long.Parse(values[3].Trim(), System.Globalization.NumberStyles.AllowThousands),
								Kills = long.Parse(values[5].Trim(), System.Globalization.NumberStyles.AllowThousands),
								Deaths = long.Parse(values[6].Trim(), System.Globalization.NumberStyles.AllowThousands),
								DamageDone = long.Parse(values[8].Trim(), System.Globalization.NumberStyles.AllowThousands),
								XPEarned = long.Parse(values[9].Trim(), System.Globalization.NumberStyles.AllowThousands),
								TimePlayed = (long)(TimeSpan.Parse(values[10].Replace("days", ".").Replace("day", ".").Replace(" ", "").Trim())).TotalSeconds,
							};
						})
						.ToList()
					);
			});

			theRequest.Wait();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Completed retrieving mech variant statistics for: {0}", PilotCallsign);

			if (theRequest.Exception != null)
				throw new Exception("Failed to parse source content: Mech Statistics", theRequest.Exception);

			return (result.Where(s => s != null).ToList());
		}

		/// <summary>
		/// Retrieve the awards assigned to the pilot
		/// </summary>
		public System.Collections.Generic.List<string> GetPilotAwards()
		{
			var profileUri = new Uri(_baseUri, new Uri("/profile", UriKind.Relative));
			var result = new System.Collections.Generic.List<string>();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Retrieving awards for: {0}", PilotCallsign);

			var theRequest = _httpClient.GetStreamAsync(profileUri).ContinueWith(s =>
			{
				var theDocument = new HtmlAgilityPack.HtmlDocument();
				theDocument.Load(s.Result);
				theDocument.OptionFixNestedTags = true;

				var theAwards = theDocument
					.GetElementbyId("titlesBox");

				if (theAwards != null)
				{
					result.AddRange(
						theAwards
							.Descendants("div")
							.Where(d => d.Attributes.Contains("class") && d.Attributes["class"].Value.Split(' ').Any(c => c.Equals("titleBox")))
							.Select(d => d.Descendants("span")
									.FirstOrDefault()
									.InnerText.Trim()
								)
						);
				}
			});

			theRequest.Wait();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Completed retrieving awards for: {0}", PilotCallsign);

			if (theRequest.Exception != null)
				throw new Exception("Failed to parse source content: Pilot Awards", theRequest.Exception);

			return (result.Where(s => s != null).ToList());
		}

		/// <summary>
		/// Retrieve pilot module statistics
		/// </summary>
		public System.Collections.Generic.List<PilotModuleStatistics> GetPilotModuleStatistics()
		{
			var moduleStatsUri = new Uri(_baseUri, new Uri("/profile/stats?type=pilot", UriKind.Relative));
			var result = new System.Collections.Generic.List<PilotModuleStatistics>();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Retrieving pilot module statistics for: {0}", PilotCallsign);

			var theRequest = _httpClient.GetStreamAsync(moduleStatsUri).ContinueWith(s =>
			{
				var theDocument = new HtmlAgilityPack.HtmlDocument();
				theDocument.Load(s.Result);
				theDocument.OptionFixNestedTags = true;

				result.AddRange(
						theDocument.DocumentNode
							.Descendants("table")
							.FirstOrDefault()
							.Descendants("tbody")
							.FirstOrDefault()
							.Descendants("tr")
							.Select(dn =>
							{
								var values = dn.Descendants("td").Select(td => td.InnerText).ToArray();

								if (values.Length == 1)
									//	When the pilot has no missions, there is a single cell with 'no data' text
									return (null);

								return new PilotModuleStatistics
								{
									PilotModule = values[0],
									MatchesPlayed = long.Parse(values[1].Trim(), System.Globalization.NumberStyles.AllowThousands),
									Wins = long.Parse(values[2].Trim(), System.Globalization.NumberStyles.AllowThousands),
									Losses = long.Parse(values[3].Trim(), System.Globalization.NumberStyles.AllowThousands),
								};
							})
							.ToList()
					);
			});

			theRequest.Wait();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Completed retrieving pilot module statistics for: {0}", PilotCallsign);

			if (theRequest.Exception != null)
				throw new Exception("Failed to parse source content: Pilot Module Statistics", theRequest.Exception);

			return (result.Where(s => s != null).ToList());
		}

		/// <summary>
		/// Retrieve weapon statistics
		/// </summary>
		public System.Collections.Generic.List<WeaponSystemStatistics> GetWeaponStatistics()
		{
			var weaponStatsUri = new Uri(_baseUri, new Uri("/profile/stats?type=weapon", UriKind.Relative));
			var result = new System.Collections.Generic.List<WeaponSystemStatistics>();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Retrieving weapon statistics for: {0}", PilotCallsign);

			var theRequest = _httpClient.GetStreamAsync(weaponStatsUri).ContinueWith(s =>
			{
				var theDocument = new HtmlAgilityPack.HtmlDocument();
				theDocument.Load(s.Result);
				theDocument.OptionFixNestedTags = true;

				result.AddRange(
						theDocument.DocumentNode
							.Descendants("table")
							.FirstOrDefault()
							.Descendants("tbody")
							.FirstOrDefault()
							.Descendants("tr")
							.Select(dn =>
							{
								var values = dn.Descendants("td").Select(td => td.InnerText).ToArray();

								if (values.Length == 1)
									//	When the pilot has no missions, there is a single cell with 'no data' text
									return (null);

								return new WeaponSystemStatistics
								{
									WeaponSystem = values[0],
									MatchesPlayed = long.Parse(values[1].Trim(), System.Globalization.NumberStyles.AllowThousands),
									Fired = long.Parse(values[2].Trim(), System.Globalization.NumberStyles.AllowThousands),
									Hits = long.Parse(values[3].Trim(), System.Globalization.NumberStyles.AllowThousands),
									TimePlayed = (long)(TimeSpan.Parse(values[5].Replace("days", ".").Replace("day", ".").Replace(" ", "").Trim())).TotalSeconds,
									DamageDone = long.Parse(values[6].Trim(), System.Globalization.NumberStyles.AllowThousands),
								};
							})
							.ToList()
					);
			});

			theRequest.Wait();

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "Completed retrieving weapon statistics for: {0}", PilotCallsign);

			if (theRequest.Exception != null)
				throw new Exception("Failed to parse source content: Weapon Statistics", theRequest.Exception);

			return (result.Where(s => s != null).ToList());
		}

		/// <summary>
		/// Authenticate with the source
		/// </summary>
		public string Login(string mwoAccountName, string mwoAccountPassword)
		{
			var loginUri = new Uri(_baseUri, new Uri("/do/login", UriKind.Relative));
			var profileUri = new Uri(_baseUri, new Uri("/profile", UriKind.Relative));

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, string.Format("Beginning Login for {0}", mwoAccountName));

			var loginRequest = _httpClient.PostAsync(loginUri, new System.Net.Http.FormUrlEncodedContent(
					new System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string, string>> {
						new System.Collections.Generic.KeyValuePair<string, string>("email", mwoAccountName),
						new System.Collections.Generic.KeyValuePair<string, string>("password", mwoAccountPassword),
					}));

			var loginResponse = loginRequest.Result;

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, string.Format("Received Login response for {0}", mwoAccountName));

			if (loginResponse.IsSuccessStatusCode == false)
				throw new Exception(loginResponse.ReasonPhrase);

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, string.Format("Beginning Profile Extract for {0}", mwoAccountName));

			var profileRequest = _httpClient.GetAsync(profileUri);
			var profileResponse = profileRequest.Result;

			if (profileResponse.IsSuccessStatusCode == false)
			{
				if (profileResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized)
					return (string.Empty);

				throw new Exception(profileResponse.ReasonPhrase);
			}

			Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, string.Format("Received Profile Extract for {0}", mwoAccountName));

			var profileDocument = new HtmlAgilityPack.HtmlDocument();

			profileDocument.Load(profileResponse.Content.ReadAsStreamAsync().Result);
			profileDocument.OptionFixNestedTags = true;
			profileDocument.OptionCheckSyntax = false;

			var userSessionActionText = profileDocument
				.GetElementbyId("topMenu")
				.Descendants("li")
				.LastOrDefault()
				.Descendants("a")
				.LastOrDefault()
				.InnerText;

			if (string.IsNullOrWhiteSpace(userSessionActionText) == false)
			{
				Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, string.Format("User Session State for {0} is \'{1}\'", mwoAccountName, userSessionActionText));

				if (userSessionActionText.Equals("LOGOUT", StringComparison.InvariantCultureIgnoreCase) == false)
					//	Login failed
					return (string.Empty);
			}
			else
				Trace.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, string.Format("User Session State for {0} is BLANK", mwoAccountName));

			string profileCallsign = null;

			var profileCallsignWrapper = profileDocument
				.DocumentNode
				.Descendants("div")
				.Where(d => d.Attributes.Contains("class") && d.Attributes["class"].Value.Split(' ').Any(c => c.Equals("profilePilotName")))
				.FirstOrDefault();

			if (profileCallsignWrapper != null)
			{
				var profileCallsignContainer = profileCallsignWrapper
					.Descendants("h1")
					.FirstOrDefault();

				if (profileCallsignContainer != null)
					profileCallsign = profileCallsignContainer.InnerText;
			}

			if (string.IsNullOrWhiteSpace(profileCallsign) == true)
				throw new Exception("Failed to parse source content");

			//	Record for diagnostics
			PilotCallsign = profileCallsign.Trim();

			return (profileCallsign.Trim());
		}
	}
}